#! /usr/bin/env python3

from ctypes import CDLL, POINTER, byref, c_char, c_char_p, c_int, c_long, c_void_p, create_string_buffer, sizeof, Structure
from dataclasses import dataclass
from logging import info
from typing import List, Union

from config import Config

class GSError(RuntimeError):
	'''
	Exception type for all errors from underlying C library.
	'''
	def __init__(self, gs_error):
		self.gs_error = gs_error

	def __str__(self):
		return 'Ghostscript exception {}'.format(self.gs_error)

class RevisionStruct(Structure):
	_fields_ = [
		('product', c_char_p),
		('copyright', c_char_p),
		('revision', c_long),
		('revisiondate', c_long),
	]

@dataclass
class Revision:
	product: str
	copyright: str
	revision: int
	revisiondate: int

class GS(object):

	_lib: str
	_printer: str

	instance = None

	def __init__(self, printer: str = None) -> None:
		#self._lib = CDLL(environ.get('GSAPI_LIB'))
		with Config() as config:
			dll: str = config.get('Ghostscript', 'dll')
			if not dll:
				raise GSError('DLL location NOT set')
			try:
				self._lib = CDLL(dll)
			except OSError:
				raise GSError('DLL load failed')
		self._printer = printer

	def __enter__(self):
		return self

	def __exit__(self, exc_type, exc_value, traceback):
		if self.instance:
			self._lib.gsapi_delete_instance(self.instance)

	def _init_instance(self):
		args: List[str] = [
			'gswin64c.exe',
			'-q',
			'-sDEVICE=mswinpr2',
			'-dNoCancel=true',
			#'-dPDFFitPage',
			#'-dDEBUG',
			'-o',
			'%printer%{}'.format(self._printer)
		]

		self.instance = c_void_p()
		e = self._lib.gsapi_new_instance(byref(self.instance), c_void_p(None))
		if e < 0:
			raise GSError(e)

		e = self._lib.gsapi_set_arg_encoding(self.instance, 1) #UTF-8
		if e < 0:
			raise GSError(e)

		_pchar = POINTER(c_char)
		argc = len(args)
		argv = (_pchar * (argc + 1))()
		for i, arg in enumerate(args):
			enc_arg = arg.encode('utf-8')
			argv[i] = create_string_buffer(enc_arg)
		argv[argc] = None
		e = self._lib.gsapi_init_with_args(self.instance, argc, argv)
		if e < 0:
			raise GSError(e)

	def revision(self) -> Revision:
		'''
		Returns a gsapi_revision_t.
		'''
		# [unicode: we assume that underlying gsapi_revision() returns utf-8
		# strings.]
		struct = RevisionStruct()
		e = self._lib.gsapi_revision(byref(struct), sizeof(struct))
		if e < 0:
			raise GSError(e)

		return Revision(
			struct.product.decode('utf-8'),
			struct.copyright.decode('utf-8'),
			struct.revision,
			struct.revisiondate,
		)

	def print(self, content: Union[str, bytes]) -> None:
		self._init_instance()

		pexit_code = c_int()
		if isinstance(content, str):
			info('Printing file %s', content)
			e = self._lib.gsapi_run_file(self.instance, content.encode(), 0, byref(pexit_code))
			if e < 0:
				raise GSError(e)
		else:
			self._lib.gsapi_run_string_begin(self.instance, 0, byref(pexit_code))

			for i in range(0, len(content), 65535):
				piece = content[i: i + 65535]
				self._lib.gsapi_run_string_continue(self.instance, piece, len(piece), 0, byref(pexit_code))

			self._lib.gsapi_run_string_end(self.instance, 0, byref(pexit_code))
		self._lib.gsapi_exit(self.instance)
